/*
 * Flashlib - QB9 common Flash library
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package com.qb9.flashlib.geom
{
	import com.qb9.flashlib.interfaces.ILocation;
	
	/**
	 * Immutable 2D vector. This class is similar to flash.geom.Point, but
	 * with the following differences:
	 *
	 * <ul>
	 *   <li>Vector2D is an immutable class. This means that once created, a Vector2D will never change its value.
	 *       This often results in more maintainable code, since you can share Vector2D instances
	 *       around without being afraid that they will change without your knowledge.</li>
	 *   <li>Some CPU-intensive operations perform faster using Vector2D than Point (eg Vector2D.distance2() vs Point.distance()).</li>
	 *   <li>Vector2D includes several methods that are not defined in the Point class.</li>
	 * </ul>
	 */
	public class Vector2D implements ILocation
	{
		private var _x:Number;
		private var _y:Number;
		
		public function get x():Number { return _x; }
		public function get y():Number { return _y; }

		public function Vector2D(x:Number = 0, y:Number = 0)
		{
			_x = x;
			_y = y;
		}
		
		/**
		 * Dot product.
		 */
		public function dot(other:Vector2D):Number
		{
			return this._x * other._x + this._y * other._y;
		}
		
		/**
		 * Vector addition.
		 */
		public function add(other:Vector2D):Vector2D
		{
			return new Vector2D(this._x + other._x, this._y + other._y);
		}
		
		/**
		 * Vector subtraction (this - other).
		 */
		public function sub(other:Vector2D):Vector2D
		{
			return new Vector2D(this._x - other._x, this._y - other._y);
		}
		
		/**
		 * Return the negated vector.
		 */
		public function neg():Vector2D
		{
			return new Vector2D(-this._x, -this._y);
		}
		
		/**
		 * Scale vector by a scalar value.
		 */
		public function scaled(scale:Number):Vector2D
		{
			return new Vector2D(this._x * scale, this._y * scale);
		}
		
		/**
		 * Distance to the given vector.
		 */
		public function distance(other:Vector2D):Number
		{
			return Math.sqrt(this.distance2(other));
		}
		
		/**
		 * Squared vector distance. This is faster than <code>distance()</code> since the square root is not computed.
		 */
		public function distance2(other:Vector2D):Number
		{
			var dx:Number = this._x - other._x;
			var dy:Number = this._y - other._y;
			return dx * dx + dy * dy;
		}
		
		/**
		 * Squared vector norm. This is faster than <code>norm</code> since the square root is not computed.
		 */
		public function get norm2():Number
		{
			return this._x * this._x + this._y * this._y;
		}
		
		/**
		 * Vector norm (distance to the origin).
		 */
		public function get norm():Number
		{
			return Math.sqrt(this.norm2);
		}
		
		/**
		 * Normailze vector.
		 */
		public function normalized(newNorm:Number = 1):Vector2D
		{
			var n:Number = this.norm;
			newNorm /= n;
			return new Vector2D(this._x * newNorm, this._y * newNorm);
		}
		
		/**
		 * Project a vector over another one.
		 */
		public function projectOver(projector:Vector2D):Vector2D
		{
			var n2:Number = projector.norm2;
			return projector.scaled(this.dot(projector) / n2);
		}
		
		/**
		 * Get a perpendicular vector (left handed).
		 */
		public function normal():Vector2D
		{
			return new Vector2D(this._y, -this._x);
		}
		
		/**
		 * Return a new vector rotated by 'radians'.
		 */
		public function rotated(radians:Number):Vector2D
		{
			return this.toPhase(this.phase + radians);
		}
		
		/**
		 * Return a new vector with same norm and 'radians' absolute phase.
		 */
		public function toPhase(radians:Number):Vector2D
		{
			return Vector2D.polar(this.norm, radians);
		}
		
		/**
		 * Vector phase, in radians.
		 */
		public function get phase():Number
		{
			return Math.atan2(this._y, this._x);
		}
		
		/**
		 * Create a vector from polar coordinates.
		 */
		static public function polar(norm:Number, radians:Number):Vector2D
		{
			return new Vector2D(norm * Math.cos(radians), norm * Math.sin(radians));
		}

		/**
		 * Linear interpolation between two vectors.
		 *
		 * @param value Interpolation value, normally between 0 and 1.
		 * @param v1 Vector that will be returned when <code>value = 0</code>.
		 * @param v2 Vector that will be returned when <code>value = 1</code>.
		 */
		static public function interpolate(value:Number, v1:Vector2D, v2:Vector2D):Vector2D
		{
			return v1.add(v2.sub(v1).scaled(value));
		}

		public function equals(other:Vector2D):Boolean
		{
			return (this._x == other._x) && (this._y == other._y);
		}
		
		public function toString():String
		{
			return "<Vector2D x:" + this._x + " y:" + this._y + ">";
		}
	}
}
